package com.sun.mybatisplus.log.aspect;

import cn.hutool.core.convert.Convert;
import cn.hutool.core.util.StrUtil;
import cn.hutool.core.util.URLUtil;
import cn.hutool.extra.servlet.ServletUtil;
import com.alibaba.fastjson.JSONObject;
import com.mysql.cj.util.StringUtils;
import com.sun.mybatisplus.entity.R;
import com.sun.mybatisplus.log.entity.OptLogDTO;
import com.sun.mybatisplus.log.event.SysLogEvent;
import com.sun.mybatisplus.utils.LogUtil;
import io.swagger.annotations.Api;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;
import java.util.Arrays;
import java.util.Objects;
import java.util.function.Consumer;

/**
 * 系统日志切面类
 */
@Aspect
@Slf4j
@Component
public class SysLogAspect {

    @Autowired
    private ApplicationContext applicationContext;

    private static final ThreadLocal<OptLogDTO> THREAD_LOCAL = new ThreadLocal<>();

    /**
     * 切入点
     */
    @Pointcut("@annotation(com.sun.mybatisplus.log.annotation.SysLog)")
    public void sysLogAdvice() {
    }


    private OptLogDTO get() {
        OptLogDTO optLogDTO = THREAD_LOCAL.get();
        if (optLogDTO == null) {
            return new OptLogDTO();
        }
        return optLogDTO;
    }


    /**
     * 前置通知
     * 收集日志操作的相关信息封装成OptLogDTO，存储到THREAD_LOCAL中
     *
     * @param joinPoint
     */
    @Before("sysLogAdvice()")
    public void recordLog(JoinPoint joinPoint) {
        tryCatch((val) -> {
            OptLogDTO sysLog = get();
            String controllerDescription = "";
            Api api = joinPoint.getTarget().getClass().getAnnotation(Api.class);
            if (api != null) {
                String[] tags = api.tags();
                if (tags != null && tags.length > 0) {
                    controllerDescription = tags[0];
                }
            }
            String controllerMethodDescription = LogUtil.getControllerMethodDescription(joinPoint);
            if (StringUtils.isNullOrEmpty(controllerDescription)) {
                sysLog.setDescription(controllerMethodDescription);
            } else {
                sysLog.setDescription(controllerDescription + "-" + controllerMethodDescription);
            }
            //类名
            sysLog.setClassPath(joinPoint.getTarget().getClass().getName());
            //方法名
            sysLog.setHttpMethod(joinPoint.getSignature().getName());
            //获取参数
            Object[] args = joinPoint.getArgs();
            String strArgs = "";
            HttpServletRequest request = ((ServletRequestAttributes) Objects.requireNonNull(RequestContextHolder.getRequestAttributes())).getRequest();
            try {
                if (!request.getContentType().contains("multipart/form-data")) {
                    strArgs = JSONObject.toJSONString(args);
                }
            } catch (Exception e) {
                try {
                    strArgs = Arrays.toString(args);
                } catch (Exception ex) {
                    log.warn("解析参数异常", ex);
                }
            }
            sysLog.setParams(strArgs);
            if (request != null) {
                sysLog.setRequestIp(ServletUtil.getClientIP(request));
                sysLog.setRequestUri(URLUtil.getPath(request.getRequestURI()));
                sysLog.setHttpMethod(request.getMethod());
                sysLog.setUa(StrUtil.sub(request.getHeader("user-agent"), 0, 500));
            }
            sysLog.setStartTime(LocalDateTime.now());
            //重新放回THREAD_LOCAL中
            THREAD_LOCAL.set(sysLog);
        });
    }


    /**
     * 异常的捕获
     *
     * @param consumer
     */
    private void tryCatch(Consumer<String> consumer) {
        try {
            consumer.accept("");
        } catch (Exception e) {
            log.warn("记录操作日志异常", e);
            THREAD_LOCAL.remove();
        }
    }


    /**
     * 异常通知
     *
     * @param e
     */
    @AfterThrowing(pointcut = "sysLogAdvice()", throwing = "e")
    public void doAfterThrowable(Throwable e) {
        tryCatch((val) -> {
            OptLogDTO sysLog = get();
            sysLog.setType("EX");
            // 异常对象
            sysLog.setExDetail(LogUtil.getStackTrace(e));
            // 异常信息
            sysLog.setExDesc(e.getMessage());

            publishEvent(sysLog);
        });
    }


    /**
     * 后置通知，方法执行完成后。从THREAD_LOCAL获取日志信息，进行后续的操作。比如保存到数据库中
     */
    @AfterReturning(returning = "result", pointcut = "sysLogAdvice()")
    public void operateLog(Object result) {
        tryCatch((val) -> {
            R r = Convert.convert(R.class, result);
            OptLogDTO sysLog = get();
            if (r == null) {
                sysLog.setType("OPT");
            } else {
                if (r.getIsSuccess()) {
                    sysLog.setType("OPT");
                } else {
                    sysLog.setType("EX");
                    sysLog.setExDetail(r.getMsg());
                }
                sysLog.setResult(r.toString());
            }
            publishEvent(sysLog);
        });
    }


    /**
     * 发布事件
     */
    private void publishEvent(OptLogDTO optLogDTO) {
        optLogDTO.setFinishTime(LocalDateTime.now());
        optLogDTO.setConsumingTime(optLogDTO.getStartTime().until(optLogDTO.getFinishTime(), ChronoUnit.MILLIS));
        //发布时间
        applicationContext.publishEvent(new SysLogEvent(optLogDTO));
        //移除信息
        THREAD_LOCAL.remove();
    }
}
